aviutl2\generic\binding/
project.rs1use crate::load_wide_string;
2
3pub struct ProjectFile<'a> {
5 pub(crate) internal: *mut aviutl2_sys::plugin2::PROJECT_FILE,
6 _marker: std::marker::PhantomData<&'a ()>,
7}
8
9#[derive(thiserror::Error, Debug)]
11pub enum ProjectFileError {
12 #[error("key contains null byte: {0}")]
13 KeyContainsNull(std::ffi::NulError),
14 #[error("data retrieval failed for key {0}")]
15 RetrievalFailed(String),
16 #[error("data length exceeds 4096 bytes, got {0} bytes")]
17 DataTooLarge(usize),
18 #[error("value contains null byte: {0}")]
19 ValueContainsNull(std::ffi::NulError),
20}
21
22impl<'a> ProjectFile<'a> {
23 pub unsafe fn from_raw(raw: *mut aviutl2_sys::plugin2::PROJECT_FILE) -> Self {
29 Self {
30 internal: raw,
31 _marker: std::marker::PhantomData,
32 }
33 }
34
35 pub fn get_param_string(&self, key: &str) -> Result<String, ProjectFileError> {
42 let c_key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
43 unsafe {
44 let raw_str = ((*self.internal).get_param_string)(c_key.as_ptr() as _);
45 if raw_str.is_null() {
46 return Err(ProjectFileError::RetrievalFailed(key.to_string()));
47 }
48 Ok(std::ffi::CStr::from_ptr(raw_str)
49 .to_string_lossy()
50 .into_owned())
51 }
52 }
53
54 pub fn get_param_binary(&self, key: &str, data: &mut [u8]) -> Result<(), ProjectFileError> {
62 let success = unsafe {
63 let key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
64 ((*self.internal).get_param_binary)(
65 key.as_ptr() as _,
66 data.as_mut_ptr() as _,
67 data.len() as _,
68 )
69 };
70 if !success {
71 return Err(ProjectFileError::RetrievalFailed(key.to_string()));
72 }
73 Ok(())
74 }
75
76 pub fn set_param_string(&mut self, key: &str, value: &str) -> Result<(), ProjectFileError> {
82 let key_cstr = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
83 let value_cstr =
84 std::ffi::CString::new(value).map_err(ProjectFileError::ValueContainsNull)?;
85 unsafe {
86 ((*self.internal).set_param_string)(key_cstr.as_ptr() as _, value_cstr.as_ptr() as _);
87 }
88 Ok(())
89 }
90
91 pub fn set_param_binary(&mut self, key: &str, data: &[u8]) -> Result<(), ProjectFileError> {
98 if data.len() > 4096 {
99 return Err(ProjectFileError::DataTooLarge(data.len()));
100 }
101 unsafe {
102 let key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
103 ((*self.internal).set_param_binary)(
104 key.as_ptr() as _,
105 data.as_ptr() as _,
106 data.len() as _,
107 );
108 }
109 Ok(())
110 }
111
112 pub fn clear_params(&mut self) {
114 unsafe { ((*self.internal).clear_params)() }
115 }
116
117 pub fn get_path(&self) -> Option<std::path::PathBuf> {
119 unsafe {
120 let raw_str = ((*self.internal).get_project_file_path)();
121 if raw_str.is_null() {
122 return None;
123 }
124 Some(std::path::PathBuf::from(load_wide_string(raw_str)))
125 }
126 }
127}
128
129#[cfg(feature = "serde")]
130const _: () = {
131 use std::io::Read;
132
133 static NAMESPACE: &str = "--aviutl2-rs";
134
135 #[derive(thiserror::Error, Debug)]
137 pub enum ProjectFileSerdeError {
138 #[error("serialization error: {0}")]
139 Serialization(#[from] rmp_serde::encode::Error),
140 #[error("deserialization error: {0}")]
141 Deserialization(#[from] rmp_serde::decode::Error),
142 #[error("zstd dompression error: {0}")]
143 Decompression(#[from] std::io::Error),
144 #[error("project file error: {0}")]
145 ProjectFile(#[from] ProjectFileError),
146 #[error("unsupported serialization format")]
147 UnsupportedFormat,
148 #[error("invalid header format: {0}")]
149 InvalidHeaderFormat(String),
150 #[error("incomplete data retrieved for key")]
151 IncompleteData,
152 }
153
154 impl<'a> ProjectFile<'a> {
155 pub fn serialize<T: serde::Serialize>(
165 &mut self,
166 key: &str,
167 value: &T,
168 ) -> Result<(), ProjectFileSerdeError> {
169 let bytes = rmp_serde::to_vec_named(value)?;
170 let num_bytes = bytes.len();
171 self.set_param_string(key, &format!("{NAMESPACE}:serde-rmp-v1:{}", num_bytes))?;
172 for (i, chunk) in bytes.chunks(4096).enumerate() {
173 let chunk_key = format!("{NAMESPACE}:serde-chunk:{}:{}", key, i);
174 self.set_param_binary(&chunk_key, chunk)?;
175 }
176 Ok(())
177 }
178
179 pub fn deserialize<T: serde::de::DeserializeOwned>(
181 &self,
182 key: &str,
183 ) -> Result<T, ProjectFileSerdeError> {
184 let header = self.get_param_string(key)?;
185 if let Ok(value) = self.decode_serde_zstd_v1(key, &header) {
186 return Ok(value);
187 }
188 self.decode_serde_rmp_v1(key, &header)
189 }
190
191 fn decode_serde_rmp_v1<T: serde::de::DeserializeOwned>(
192 &self,
193 key: &str,
194 header: &str,
195 ) -> Result<T, ProjectFileSerdeError> {
196 let header_prefix = format!("{NAMESPACE}:serde-rmp-v1:");
197 let num_bytes = header
198 .strip_prefix(&header_prefix)
199 .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
200 let num_bytes: usize = num_bytes
201 .parse()
202 .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
203 if num_bytes == 0 {
204 return Err(ProjectFileSerdeError::InvalidHeaderFormat(
205 header.to_string(),
206 ));
207 }
208 let chunks = self.collect_chunks(num_bytes, key)?;
209 let value: T = rmp_serde::from_slice(&chunks)?;
210 Ok(value)
211 }
212
213 fn collect_chunks(
214 &self,
215 num_bytes: usize,
216 key: &str,
217 ) -> Result<Vec<u8>, ProjectFileSerdeError> {
218 let mut bytes = Vec::with_capacity(num_bytes);
219 let mut read_bytes = 0;
220 let mut chunk = vec![0u8; 4096];
221 for i in 0.. {
222 let chunk_key = format!("{NAMESPACE}:serde-chunk:{}:{}", key, i);
223 let to_read = std::cmp::min(4096, num_bytes - read_bytes);
224 chunk.resize(to_read, 0);
225 match self.get_param_binary(&chunk_key, &mut chunk) {
226 Ok(()) => {
227 bytes.extend_from_slice(&chunk);
228 read_bytes += to_read;
229 if read_bytes >= num_bytes {
230 break;
231 }
232 }
233 Err(_) => break,
234 }
235 }
236 if read_bytes != num_bytes {
237 return Err(ProjectFileSerdeError::IncompleteData);
238 }
239 Ok(bytes)
240 }
241 fn decode_serde_zstd_v1<T: serde::de::DeserializeOwned>(
242 &self,
243 key: &str,
244 header: &str,
245 ) -> Result<T, ProjectFileSerdeError> {
246 let header_prefix = format!("{NAMESPACE}:serde-zstd-v1:");
247 let num_bytes = header
248 .strip_prefix(&header_prefix)
249 .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
250 let num_bytes: usize = num_bytes
251 .parse()
252 .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
253 if num_bytes == 0 {
254 return Err(ProjectFileSerdeError::InvalidHeaderFormat(
255 header.to_string(),
256 ));
257 }
258 let mut bytes = Vec::with_capacity(num_bytes);
259 let mut read_bytes = 0;
260 let mut chunk = vec![0u8; 4096];
261 for i in 0.. {
262 let chunk_key = format!("{NAMESPACE}:serde-zstd-v1:chunk:{}:{}", key, i);
263 let to_read = std::cmp::min(4096, num_bytes - read_bytes);
264 chunk.resize(to_read, 0);
265 match self.get_param_binary(&chunk_key, &mut chunk) {
266 Ok(()) => {
267 bytes.extend_from_slice(&chunk);
268 read_bytes += to_read;
269 if read_bytes >= num_bytes {
270 break;
271 }
272 }
273 Err(_) => break,
274 }
275 }
276 if read_bytes != num_bytes {
277 return Err(ProjectFileSerdeError::IncompleteData);
278 }
279 let mut decoder = ruzstd::decoding::StreamingDecoder::new(&bytes[..])
280 .map_err(|e| ProjectFileSerdeError::Decompression(std::io::Error::other(e)))?;
281 let mut decompressed_bytes = vec![];
282 decoder.read_to_end(&mut decompressed_bytes)?;
283 let value: T = rmp_serde::from_slice(&decompressed_bytes)?;
284 Ok(value)
285 }
286 }
287};